<template>
    <view class="uni-datetime-picker">
        <view @click="initTimePicker">
            <slot>
                <view class="uni-datetime-picker-timebox-pointer"
                      :class="{'uni-datetime-picker-disabled': disabled, 'uni-datetime-picker-timebox': border}">
                    <text class="uni-datetime-picker-text">{{time}}</text>
                    <view v-if="!time" class="uni-datetime-picker-time">
                        <text class="uni-datetime-picker-text">{{selectTimeText}}</text>
                    </view>
                </view>
            </slot>
        </view>
        <view v-if="visible" id="mask" class="uni-datetime-picker-mask" @click="tiggerTimePicker"></view>
        <view v-if="visible" class="uni-datetime-picker-popup" :class="[dateShow && timeShow ? '' : 'fix-nvue-height']"
              :style="fixNvueBug">
            <view class="uni-title">
                <text class="uni-datetime-picker-text">{{selectTimeText}}</text>
            </view>
            <view v-if="dateShow" class="uni-datetime-picker__container-box">
                <picker-view class="uni-datetime-picker-view" :indicator-style="indicatorStyle" :value="ymd"
                             @change="bindDateChange">
                    <picker-view-column>
                        <view class="uni-datetime-picker-item" v-for="(item,index) in years" :key="index">
                            <text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
                        </view>
                    </picker-view-column>
                    <picker-view-column>
                        <view class="uni-datetime-picker-item" v-for="(item,index) in months" :key="index">
                            <text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
                        </view>
                    </picker-view-column>
                    <picker-view-column>
                        <view class="uni-datetime-picker-item" v-for="(item,index) in days" :key="index">
                            <text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
                        </view>
                    </picker-view-column>
                </picker-view>
                <!-- 兼容 nvue 不支持伪类 -->
                <text class="uni-datetime-picker-sign sign-left">-</text>
                <text class="uni-datetime-picker-sign sign-right">-</text>
            </view>
            <view v-if="timeShow" class="uni-datetime-picker__container-box">
                <picker-view class="uni-datetime-picker-view" :class="[hideSecond ? 'time-hide-second' : '']"
                             :indicator-style="indicatorStyle" :value="hms" @change="bindTimeChange">
                    <picker-view-column>
                        <view class="uni-datetime-picker-item" v-for="(item,index) in hours" :key="index">
                            <text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
                        </view>
                    </picker-view-column>
                    <picker-view-column>
                        <view class="uni-datetime-picker-item" v-for="(item,index) in minutes" :key="index">
                            <text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
                        </view>
                    </picker-view-column>
                    <picker-view-column v-if="!hideSecond">
                        <view class="uni-datetime-picker-item" v-for="(item,index) in seconds" :key="index">
                            <text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text>
                        </view>
                    </picker-view-column>
                </picker-view>
                <!-- 兼容 nvue 不支持伪类 -->
                <text class="uni-datetime-picker-sign" :class="[hideSecond ? 'sign-center' : 'sign-left']">:</text>
                <text v-if="!hideSecond" class="uni-datetime-picker-sign sign-right">:</text>
            </view>
            <view class="uni-datetime-picker-btn">
                <view @click="clearTime">
                    <text class="uni-datetime-picker-btn-text">{{clearText}}</text>
                </view>
                <view class="uni-datetime-picker-btn-group">
                    <view class="uni-datetime-picker-cancel" @click="tiggerTimePicker">
                        <text class="uni-datetime-picker-btn-text">{{cancelText}}</text>
                    </view>
                    <view @click="setTime">
                        <text class="uni-datetime-picker-btn-text">{{okText}}</text>
                    </view>
                </view>
            </view>
        </view>
        <!-- #ifdef H5 -->
        <!-- <keypress v-if="visible" @esc="tiggerTimePicker" @enter="setTime" /> -->
        <!-- #endif -->
    </view>
</template>

<script>
    // #ifdef H5
    import keypress from './keypress'
    // #endif
    import {
        initVueI18n
    } from '@dcloudio/uni-i18n'
    import messages from './i18n/index.js'

    const {t} = initVueI18n(messages)

    /**
     * DatetimePicker 时间选择器
     * @description 可以同时选择日期和时间的选择器
     * @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
     * @property {String} type = [datetime | date | time] 显示模式
     * @property {Boolean} multiple = [true|false] 是否多选
     * @property {String|Number} value 默认值
     * @property {String|Number} start 起始日期或时间
     * @property {String|Number} end 起始日期或时间
     * @property {String} return-type = [timestamp | string]
     * @event {Function} change  选中发生变化触发
     */

    export default {
        name: 'UniDatetimePicker',
        components: {
            // #ifdef H5
            keypress
            // #endif
        },
        data() {
            return {
                indicatorStyle: `height: 50px;`,
                visible: false,
                fixNvueBug: {},
                dateShow: true,
                timeShow: true,
                title: '日期和时间',
                // 输入框当前时间
                time: '',
                // 当前的年月日时分秒
                year: 1920,
                month: 0,
                day: 0,
                hour: 0,
                minute: 0,
                second: 0,
                // 起始时间
                startYear: 1920,
                startMonth: 1,
                startDay: 1,
                startHour: 0,
                startMinute: 0,
                startSecond: 0,
                // 结束时间
                endYear: 2120,
                endMonth: 12,
                endDay: 31,
                endHour: 23,
                endMinute: 59,
                endSecond: 59,
            }
        },
        props: {
            type: {
                type: String,
                default: 'datetime'
            },
            value: {
                type: [String, Number],
                default: ''
            },
            modelValue: {
                type: [String, Number],
                default: ''
            },
            start: {
                type: [Number, String],
                default: ''
            },
            end: {
                type: [Number, String],
                default: ''
            },
            returnType: {
                type: String,
                default: 'string'
            },
            disabled: {
                type: [Boolean, String],
                default: false
            },
            border: {
                type: [Boolean, String],
                default: true
            },
            hideSecond: {
                type: [Boolean, String],
                default: false
            }
        },
        watch: {
            value: {
                handler(newVal, oldVal) {
                    if (newVal) {
                        this.parseValue(this.fixIosDateFormat(newVal)) //兼容 iOS、safari 日期格式
                        this.initTime(false)
                    } else {
                        this.time = ''
                        this.parseValue(Date.now())
                    }
                },
                immediate: true
            },
            type: {
                handler(newValue) {
                    if (newValue === 'date') {
                        this.dateShow = true
                        this.timeShow = false
                        this.title = '日期'
                    } else if (newValue === 'time') {
                        this.dateShow = false
                        this.timeShow = true
                        this.title = '时间'
                    } else {
                        this.dateShow = true
                        this.timeShow = true
                        this.title = '日期和时间'
                    }
                },
                immediate: true
            },
            start: {
                handler(newVal) {
                    this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'start') //兼容 iOS、safari 日期格式
                },
                immediate: true
            },
            end: {
                handler(newVal) {
                    this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'end') //兼容 iOS、safari 日期格式
                },
                immediate: true
            },

            // 月、日、时、分、秒可选范围变化后，检查当前值是否在范围内，不在则当前值重置为可选范围第一项
            months(newVal) {
                this.checkValue('month', this.month, newVal)
            },
            days(newVal) {
                this.checkValue('day', this.day, newVal)
            },
            hours(newVal) {
                this.checkValue('hour', this.hour, newVal)
            },
            minutes(newVal) {
                this.checkValue('minute', this.minute, newVal)
            },
            seconds(newVal) {
                this.checkValue('second', this.second, newVal)
            }
        },
        computed: {
            // 当前年、月、日、时、分、秒选择范围
            years() {
                return this.getCurrentRange('year')
            },

            months() {
                return this.getCurrentRange('month')
            },

            days() {
                return this.getCurrentRange('day')
            },

            hours() {
                return this.getCurrentRange('hour')
            },

            minutes() {
                return this.getCurrentRange('minute')
            },

            seconds() {
                return this.getCurrentRange('second')
            },

            // picker 当前值数组
            ymd() {
                return [this.year - this.minYear, this.month - this.minMonth, this.day - this.minDay]
            },
            hms() {
                return [this.hour - this.minHour, this.minute - this.minMinute, this.second - this.minSecond]
            },

            // 当前 date 是 start
            currentDateIsStart() {
                return this.year === this.startYear && this.month === this.startMonth && this.day === this.startDay
            },

            // 当前 date 是 end
            currentDateIsEnd() {
                return this.year === this.endYear && this.month === this.endMonth && this.day === this.endDay
            },

            // 当前年、月、日、时、分、秒的最小值和最大值
            minYear() {
                return this.startYear
            },
            maxYear() {
                return this.endYear
            },
            minMonth() {
                if (this.year === this.startYear) {
                    return this.startMonth
                } else {
                    return 1
                }
            },
            maxMonth() {
                if (this.year === this.endYear) {
                    return this.endMonth
                } else {
                    return 12
                }
            },
            minDay() {
                if (this.year === this.startYear && this.month === this.startMonth) {
                    return this.startDay
                } else {
                    return 1
                }
            },
            maxDay() {
                if (this.year === this.endYear && this.month === this.endMonth) {
                    return this.endDay
                } else {
                    return this.daysInMonth(this.year, this.month)
                }
            },
            minHour() {
                if (this.type === 'datetime') {
                    if (this.currentDateIsStart) {
                        return this.startHour
                    } else {
                        return 0
                    }
                }
                if (this.type === 'time') {
                    return this.startHour
                }
            },
            maxHour() {
                if (this.type === 'datetime') {
                    if (this.currentDateIsEnd) {
                        return this.endHour
                    } else {
                        return 23
                    }
                }
                if (this.type === 'time') {
                    return this.endHour
                }
            },
            minMinute() {
                if (this.type === 'datetime') {
                    if (this.currentDateIsStart && this.hour === this.startHour) {
                        return this.startMinute
                    } else {
                        return 0
                    }
                }
                if (this.type === 'time') {
                    if (this.hour === this.startHour) {
                        return this.startMinute
                    } else {
                        return 0
                    }
                }
            },
            maxMinute() {
                if (this.type === 'datetime') {
                    if (this.currentDateIsEnd && this.hour === this.endHour) {
                        return this.endMinute
                    } else {
                        return 59
                    }
                }
                if (this.type === 'time') {
                    if (this.hour === this.endHour) {
                        return this.endMinute
                    } else {
                        return 59
                    }
                }
            },
            minSecond() {
                if (this.type === 'datetime') {
                    if (this.currentDateIsStart && this.hour === this.startHour && this.minute === this.startMinute) {
                        return this.startSecond
                    } else {
                        return 0
                    }
                }
                if (this.type === 'time') {
                    if (this.hour === this.startHour && this.minute === this.startMinute) {
                        return this.startSecond
                    } else {
                        return 0
                    }
                }
            },
            maxSecond() {
                if (this.type === 'datetime') {
                    if (this.currentDateIsEnd && this.hour === this.endHour && this.minute === this.endMinute) {
                        return this.endSecond
                    } else {
                        return 59
                    }
                }
                if (this.type === 'time') {
                    if (this.hour === this.endHour && this.minute === this.endMinute) {
                        return this.endSecond
                    } else {
                        return 59
                    }
                }
            },

            /**
             * for i18n
             */
            selectTimeText() {
                return t("uni-datetime-picker.selectTime")
            },
            okText() {
                return t("uni-datetime-picker.ok")
            },
            clearText() {
                return t("uni-datetime-picker.clear")
            },
            cancelText() {
                return t("uni-datetime-picker.cancel")
            }
        },

        mounted() {
            // #ifdef APP-NVUE
            const res = uni.getSystemInfoSync();
            this.fixNvueBug = {
                top: res.windowHeight / 2,
                left: res.windowWidth / 2
            }
            // #endif
        },

        methods: {
            /**
             * @param {Object} item
             * 小于 10 在前面加个 0
             */

            lessThanTen(item) {
                return item < 10 ? '0' + item : item
            },

            /**
             * 解析时分秒字符串，例如：00:00:00
             * @param {String} timeString
             */
            parseTimeType(timeString) {
                if (timeString) {
                    let timeArr = timeString.split(':')
                    this.hour = Number(timeArr[0])
                    this.minute = Number(timeArr[1])
                    this.second = Number(timeArr[2])
                }
            },

            /**
             * 解析选择器初始值，类型可以是字符串、时间戳，例如：2000-10-02、'08:30:00'、 1610695109000
             * @param {String | Number} datetime
             */
            initPickerValue(datetime) {
                let defaultValue = null
                if (datetime) {
                    defaultValue = this.compareValueWithStartAndEnd(datetime, this.start, this.end)
                } else {
                    defaultValue = Date.now()
                    defaultValue = this.compareValueWithStartAndEnd(defaultValue, this.start, this.end)
                }
                this.parseValue(defaultValue)
            },

            /**
             * 初始值规则：
             * - 用户设置初始值 value
             *    - 设置了起始时间 start、终止时间 end，并 start < value < end，初始值为 value， 否则初始值为 start
             *    - 只设置了起始时间 start，并 start < value，初始值为 value，否则初始值为 start
             *    - 只设置了终止时间 end，并 value < end，初始值为 value，否则初始值为 end
             *    - 无起始终止时间，则初始值为 value
             * - 无初始值 value，则初始值为当前本地时间 Date.now()
             * @param {Object} value
             * @param {Object} dateBase
             */
            compareValueWithStartAndEnd(value, start, end) {
                let winner = null
                value = this.superTimeStamp(value)
                start = this.superTimeStamp(start)
                end = this.superTimeStamp(end)

                if (start && end) {
                    if (value < start) {
                        winner = new Date(start)
                    } else if (value > end) {
                        winner = new Date(end)
                    } else {
                        winner = new Date(value)
                    }
                } else if (start && !end) {
                    winner = start <= value ? new Date(value) : new Date(start)
                } else if (!start && end) {
                    winner = value <= end ? new Date(value) : new Date(end)
                } else {
                    winner = new Date(value)
                }

                return winner
            },

            /**
             * 转换为可比较的时间戳，接受日期、时分秒、时间戳
             * @param {Object} value
             */
            superTimeStamp(value) {
                let dateBase = ''
                if (this.type === 'time' && value && typeof value === 'string') {
                    const now = new Date()
                    const year = now.getFullYear()
                    const month = now.getMonth() + 1
                    const day = now.getDate()
                    dateBase = year + '/' + month + '/' + day + ' '
                }
                if (Number(value) && typeof value !== NaN) {
                    value = parseInt(value)
                    dateBase = 0
                }
                return this.createTimeStamp(dateBase + value)
            },

            /**
             * 解析默认值 value，字符串、时间戳
             * @param {Object} defaultTime
             */
            parseValue(value) {
                if (!value) {
                    return
                }
                if (this.type === 'time' && typeof value === "string") {
                    this.parseTimeType(value)
                } else {
                    let defaultDate = null
                    defaultDate = new Date(value)
                    if (this.type !== 'time') {
                        this.year = defaultDate.getFullYear()
                        this.month = defaultDate.getMonth() + 1
                        this.day = defaultDate.getDate()
                    }
                    if (this.type !== 'date') {
                        this.hour = defaultDate.getHours()
                        this.minute = defaultDate.getMinutes()
                        this.second = defaultDate.getSeconds()
                    }
                }
                if (this.hideSecond) {
                    this.second = 0
                }
            },

            /**
             * 解析可选择时间范围 start、end，年月日字符串、时间戳
             * @param {Object} defaultTime
             */
            parseDatetimeRange(point, pointType) {
                // 时间为空，则重置为初始值
                if (!point) {
                    if (pointType === 'start') {
                        this.startYear = 1920
                        this.startMonth = 1
                        this.startDay = 1
                        this.startHour = 0
                        this.startMinute = 0
                        this.startSecond = 0
                    }
                    if (pointType === 'end') {
                        this.endYear = 2120
                        this.endMonth = 12
                        this.endDay = 31
                        this.endHour = 23
                        this.endMinute = 59
                        this.endSecond = 59
                    }
                    return
                }
                if (this.type === 'time') {
                    const pointArr = point.split(':')
                    this[pointType + 'Hour'] = Number(pointArr[0])
                    this[pointType + 'Minute'] = Number(pointArr[1])
                    this[pointType + 'Second'] = Number(pointArr[2])
                } else {
                    if (!point) {
                        pointType === 'start' ? this.startYear = this.year - 60 : this.endYear = this.year + 60
                        return
                    }
                    if (Number(point) && Number(point) !== NaN) {
                        point = parseInt(point)
                    }
                    // datetime 的 end 没有时分秒, 则不限制
                    const hasTime = /[0-9]:[0-9]/
                    if (this.type === 'datetime' && pointType === 'end' && typeof point === 'string' && !hasTime.test(
                        point)) {
                        point = point + ' 23:59:59'
                    }
                    const pointDate = new Date(point)
                    this[pointType + 'Year'] = pointDate.getFullYear()
                    this[pointType + 'Month'] = pointDate.getMonth() + 1
                    this[pointType + 'Day'] = pointDate.getDate()
                    if (this.type === 'datetime') {
                        this[pointType + 'Hour'] = pointDate.getHours()
                        this[pointType + 'Minute'] = pointDate.getMinutes()
                        this[pointType + 'Second'] = pointDate.getSeconds()
                    }
                }
            },

            // 获取 年、月、日、时、分、秒 当前可选范围
            getCurrentRange(value) {
                const range = []
                for (let i = this['min' + this.capitalize(value)]; i <= this['max' + this.capitalize(value)]; i++) {
                    range.push(i)
                }
                return range
            },

            // 字符串首字母大写
            capitalize(str) {
                return str.charAt(0).toUpperCase() + str.slice(1)
            },

            // 检查当前值是否在范围内，不在则当前值重置为可选范围第一项
            checkValue(name, value, values) {
                if (values.indexOf(value) === -1) {
                    this[name] = values[0]
                }
            },

            // 每个月的实际天数
            daysInMonth(year, month) { // Use 1 for January, 2 for February, etc.
                return new Date(year, month, 0).getDate();
            },

            //兼容 iOS、safari 日期格式
            fixIosDateFormat(value) {
                if (typeof value === 'string') {
                    value = value.replace(/-/g, '/')
                }
                return value
            },

            /**
             * 生成时间戳
             * @param {Object} time
             */
            createTimeStamp(time) {
                if (!time) return
                if (typeof time === "number") {
                    return time
                } else {
                    time = time.replace(/-/g, '/')
                    if (this.type === 'date') {
                        time = time + ' ' + '00:00:00'
                    }
                    return Date.parse(time)
                }
            },

            /**
             * 生成日期或时间的字符串
             */
            createDomSting() {
                const yymmdd = this.year +
                    '-' +
                    this.lessThanTen(this.month) +
                    '-' +
                    this.lessThanTen(this.day)

                let hhmmss = this.lessThanTen(this.hour) +
                    ':' +
                    this.lessThanTen(this.minute)

                if (!this.hideSecond) {
                    hhmmss = hhmmss + ':' + this.lessThanTen(this.second)
                }

                if (this.type === 'date') {
                    return yymmdd
                } else if (this.type === 'time') {
                    return hhmmss
                } else {
                    return yymmdd + ' ' + hhmmss
                }
            },

            /**
             * 初始化返回值，并抛出 change 事件
             */
            initTime(emit = true) {
                this.time = this.createDomSting()
                if (!emit) return
                if (this.returnType === 'timestamp' && this.type !== 'time') {
                    this.$emit('change', this.createTimeStamp(this.time))
                    this.$emit('input', this.createTimeStamp(this.time))
                    this.$emit('update:modelValue', this.createTimeStamp(this.time))
                } else {
                    this.$emit('change', this.time)
                    this.$emit('input', this.time)
                    this.$emit('update:modelValue', this.time)
                }
            },

            /**
             * 用户选择日期或时间更新 data
             * @param {Object} e
             */
            bindDateChange(e) {
                const val = e.detail.value
                this.year = this.years[val[0]]
                this.month = this.months[val[1]]
                this.day = this.days[val[2]]
            },
            bindTimeChange(e) {
                const val = e.detail.value
                this.hour = this.hours[val[0]]
                this.minute = this.minutes[val[1]]
                this.second = this.seconds[val[2]]
            },

            /**
             * 初始化弹出层
             */
            initTimePicker() {
                if (this.disabled) return
                const value = this.fixIosDateFormat(this.value)
                this.initPickerValue(value)
                this.visible = !this.visible
            },

            /**
             * 触发或关闭弹框
             */
            tiggerTimePicker(e) {
                this.visible = !this.visible
            },

            /**
             * 用户点击“清空”按钮，清空当前值
             */
            clearTime() {
                this.time = ''
                this.$emit('change', this.time)
                this.$emit('input', this.time)
                this.$emit('update:modelValue', this.time)
                this.tiggerTimePicker()
            },

            /**
             * 用户点击“确定”按钮
             */
            setTime() {
                this.initTime()
                this.tiggerTimePicker()
            }
        }
    }
</script>

<style>
    .uni-datetime-picker {
        /* #ifndef APP-NVUE */
        /* width: 100%; */
        /* #endif */
    }

    .uni-datetime-picker-view {
        height: 130px;
        width: 270px;
        /* #ifndef APP-NVUE */
        cursor: pointer;
        /* #endif */
    }

    .uni-datetime-picker-item {
        height: 50px;
        line-height: 50px;
        text-align: center;
        font-size: 14px;
    }

    .uni-datetime-picker-btn {
        margin-top: 60px;
        /* #ifndef APP-NVUE */
        display: flex;
        cursor: pointer;
        /* #endif */
        flex-direction: row;
        justify-content: space-between;
    }

    .uni-datetime-picker-btn-text {
        font-size: 14px;
        color: #007AFF;
    }

    .uni-datetime-picker-btn-group {
        /* #ifndef APP-NVUE */
        display: flex;
        /* #endif */
        flex-direction: row;
    }

    .uni-datetime-picker-cancel {
        margin-right: 30px;
    }

    .uni-datetime-picker-mask {
        position: fixed;
        bottom: 0px;
        top: 0px;
        left: 0px;
        right: 0px;
        background-color: rgba(0, 0, 0, 0.4);
        transition-duration: 0.3s;
        z-index: 998;
    }

    .uni-datetime-picker-popup {
        border-radius: 8px;
        padding: 30px;
        width: 270px;
        /* #ifdef APP-NVUE */
        height: 500px;
        /* #endif */
        /* #ifdef APP-NVUE */
        width: 330px;
        /* #endif */
        background-color: #fff;
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        transition-duration: 0.3s;
        z-index: 999;
    }

    .fix-nvue-height {
        /* #ifdef APP-NVUE */
        height: 330px;
        /* #endif */
    }

    .uni-datetime-picker-time {
        color: grey;
    }

    .uni-datetime-picker-column {
        height: 50px;
    }

    .uni-datetime-picker-timebox {

        border: 1px solid #E5E5E5;
        border-radius: 5px;
        padding: 7px 10px;
        /* #ifndef APP-NVUE */
        box-sizing: border-box;
        cursor: pointer;
        /* #endif */
    }

    .uni-datetime-picker-timebox-pointer {
        /* #ifndef APP-NVUE */
        cursor: pointer;
        /* #endif */
    }


    .uni-datetime-picker-disabled {
        opacity: 0.4;
        /* #ifdef H5 */
        cursor: not-allowed !important;
        /* #endif */
    }

    .uni-datetime-picker-text {
        font-size: 14px;
    }

    .uni-datetime-picker-sign {
        position: absolute;
        top: 53px;
        /* 减掉 10px 的元素高度，兼容nvue */
        color: #999;
        /* #ifdef APP-NVUE */
        font-size: 16px;
        /* #endif */
    }

    .sign-left {
        left: 86px;
    }

    .sign-right {
        right: 86px;
    }

    .sign-center {
        left: 135px;
    }

    .uni-datetime-picker__container-box {
        position: relative;
        display: flex;
        align-items: center;
        justify-content: center;
        margin-top: 40px;
    }

    .time-hide-second {
        width: 180px;
    }
</style>
